/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.taglib.core.html;

import com.blandware.atleap.webapp.util.core.ApplicationResources;
import com.blandware.atleap.webapp.util.core.WebappConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.struts.Globals;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.taglib.TagUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Iterator;

/**
 * <p>Displays set of ActionMessages on the page. This tag behaviour is like the
 * Struts ErrorsTag, except of some features. <br />
 * The first difference is an algorithm of obtaining messages/errors. It firstly
 * tries to obtain messages by <code>WebappConstants.MESSAGE_KEY</code>: firstly
 * from request and, if not found, from session; if nothing was found for that
 * key, then trying to obtain them from request by Struts key
 * (<code>Globals.MESSAGE_KEY</code>). If no messages were found, then errors
 * are searched: in session and (if not found) request by
 * <code>WebappConstants.ERROR_KEY</code> key and further in request by
 * <code>Globals.ERROR_KEY</code>. <br />
 * If there are no action errors or messages, this tag simply does nothing.
 * After displaying tag removes attributes from request and session, so errors
 * will be displayed only once and will not be redisplayed on page reload. <br />
 * The second difference is that this tag does not accept String or String array
 * as a set of action errors - only ActionMessages object.
 * </p>
 * <p>
 * This tag also accepts standard keys such as <code>errors.header</code>,
 * <code>errors.footer</code> etc. and if they present, they will be used to
 * display.
 * </p>
 * <p>
 * Allowed attributes are:
 * <ul>
 * <li>
 * <b>property</b> - property to display ActionMessage for. If not specified,
 * all errors will be displayed.
 * </li>
 * <li>
 * <b>stackTraceVar</b> - name of variable to save stack trace of exception in.
 * If specified and exception is found, stack trace of this exception will be
 * printed to string and saved in specified variable. Otherwise, it will be
 * enclosed in &lt;pre&gt;&lt;/pre&gt; tags and printed to JSP out.
 * </li>
 * <li>
 * <b>stackTraceVarScope</b> - scope to save variable that holds stack trace of
 * exception
 * </li>
 * </ul>
 * </p>
 * <p><a href="MessagesTag.java.html"><i>View Source</i></a></p>
 *
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.9 $ $Date: 2006/08/03 10:08:23 $
 * @jsp.tag name="messages"
 * body-content="empty"
 * @see org.apache.struts.taglib.html.ErrorsTag
 * @see org.apache.struts.action.ActionMessages
 * @see org.apache.struts.taglib.html.BaseTag
 */
public class MessagesTag extends SimpleTagSupport {

	protected transient final Log log = LogFactory.getLog(MessagesTag.class);

	/**
	 * Property to display ActionMessage for. If not specified, all errors will be displayed.
	 */
	protected String property;

	/**
	 * Variable to save stack trace of exception in. If specified and exception is found,
	 * stack trace of this exception will be printed to string and saved in specified variable.
	 * Otherwise, it will be enclosed in &lt;pre&gt;&lt;/pre&gt; tags and printed to JSP out
	 */
	protected String stackTraceVar;

	/**
	 * Scope to save variable that holds stack trace of exception
	 */
	protected String stackTraceVarScope;

	/**
     * Returns property name
     *
	 * @return property name
     * @see #property
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="The server name to use"
	 */
	public String getProperty() {
		return property;
	}

    /**
     * Sets property name
     *
     * @param property property name to set
     * @see #property
     */
	public void setProperty(String property) {
		this.property = property;
	}

	/**
     * Returns stack trace variable name
     *
	 * @return stack trace variable name
     * @see #stackTraceVar
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Variable to save stack trace of exception in"
	 */
	public String getStackTraceVar() {
		return stackTraceVar;
	}

    /**
     * Sets stack trace variable name
     *
     * @param stackTraceVar stack trace variable name to set
     * @see #stackTraceVar
     */
	public void setStackTraceVar(String stackTraceVar) {
		this.stackTraceVar = stackTraceVar;
	}

	/**
     * Returns stack trace variable scope
     *
	 * @return stack trace variable scope
     * @see #stackTraceVarScope
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Scope to save variable that holds stack trace of exception"
	 */
	public String getStackTraceVarScope() {
		return stackTraceVarScope;
	}

    /**
     * Sets stack trace variable scope
     *
     * @param stackTraceVarScope stack trace variable scope to set
     * @see #stackTraceVarScope
     */
	public void setStackTraceVarScope(String stackTraceVarScope) {
		this.stackTraceVarScope = stackTraceVarScope;
	}

    /**
     * Processes the tag
     *
     * @throws JspException
     */
	public void doTag() throws JspException {

		PageContext pageContext = (PageContext) getJspContext();

		boolean messagesPresent = true;

		// first search for messages
		ActionMessages messages = (ActionMessages) pageContext.getAttribute(WebappConstants.MESSAGE_KEY, PageContext.REQUEST_SCOPE);
		if ( !containRequestedMessage(messages) ) {
			messages = (ActionMessages) pageContext.getAttribute(WebappConstants.MESSAGE_KEY, PageContext.SESSION_SCOPE);
			if ( !containRequestedMessage(messages) ) {
				messages = (ActionMessages) pageContext.getAttribute(Globals.MESSAGE_KEY, PageContext.REQUEST_SCOPE);
			} else {
				pageContext.setAttribute(WebappConstants.MESSAGE_KEY, messages, PageContext.REQUEST_SCOPE);
				pageContext.removeAttribute(WebappConstants.MESSAGE_KEY, PageContext.SESSION_SCOPE);
			}
		}

		// if not found, search for errors
		if ( !containRequestedMessage(messages) ) {
			messagesPresent = false;
			messages = (ActionMessages) pageContext.getAttribute(WebappConstants.ERROR_KEY, PageContext.REQUEST_SCOPE);
			if ( !containRequestedMessage(messages) ) {
				messages = (ActionMessages) pageContext.getAttribute(WebappConstants.ERROR_KEY, PageContext.SESSION_SCOPE);
				if ( !containRequestedMessage(messages) ) {
					messages = (ActionMessages) pageContext.getAttribute(Globals.ERROR_KEY, PageContext.REQUEST_SCOPE);
					if ( !containRequestedMessage(messages) ) {
						messages = new ActionMessages();
					}
				} else {
					pageContext.setAttribute(WebappConstants.ERROR_KEY, messages, PageContext.REQUEST_SCOPE);
					pageContext.removeAttribute(WebappConstants.ERROR_KEY, PageContext.SESSION_SCOPE);
				}
			}
		}


		HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
		TagUtils tagUtils = TagUtils.getInstance();
		ApplicationResources applicationResources = ApplicationResources.getInstance(pageContext.getServletContext());
		StringBuffer exceptionBuffer = null;

		// if property is not specified, put exception from tags and/or actions
		if ( property == null ) {
			// search for exceptions from tags or actions and write them to the output
			Throwable t = (Throwable) pageContext.findAttribute("javax.servlet.error.exception");
			if ( t == null ) {
				t = (Throwable) pageContext.findAttribute("javax.servlet.error.jspException");
				if ( t == null ) {
					t = (Throwable) pageContext.findAttribute(Globals.EXCEPTION_KEY);
				}
			}

			if ( t != null ) {
				// get stack trace

				// look for 'rootCause' property
				// if it exists, process it instead of caught throwable
				try {
					Throwable rootCause = (Throwable) PropertyUtils.getProperty(t, "rootCause");
					if ( rootCause != null ) {
						t = rootCause;
					}
				} catch ( Exception e ) {
					// ignore it
				}
				StringWriter sw = new StringWriter();
				t.printStackTrace(new PrintWriter(sw));
				exceptionBuffer = new StringBuffer(sw.toString());

				logException(t);
			}
		}

		if ( messages != null && !messages.isEmpty() ) {

			// messages are present and not empty

			String headerKey = messagesPresent ? "core.commons.messages.header" : "core.commons.errors.header";
			String footerKey = messagesPresent ? "core.commons.messages.footer" : "core.commons.errors.footer";
			String prefixKey = messagesPresent ? "core.commons.messages.prefix" : "core.commons.errors.prefix";
			String suffixKey = messagesPresent ? "core.commons.messages.suffix" : "core.commons.errors.suffix";

			String header = applicationResources.getMessage(request, headerKey);
			String footer = applicationResources.getMessage(request, footerKey);
			String prefix = applicationResources.getMessage(request, prefixKey);
			String suffix = applicationResources.getMessage(request, suffixKey);

			Iterator reports = property == null ? messages.get() : messages.get(property);

			StringBuffer results = new StringBuffer();
			if ( header != null && property == null ) {
				results.append(header);
			}
			while ( reports.hasNext() ) {
				ActionMessage report = (ActionMessage) reports.next();
				if ( report == null ) {
					continue;
				}
				if ( prefix != null ) {
					results.append(prefix);
				}

				String message = applicationResources.getMessage(request, report.getKey(), report.getValues());
				if ( message != null ) {
					results.append(message);
				}

				if ( suffix != null ) {
					results.append(suffix);
				}

			}

			if ( header != null && footer != null && property == null ) {
				results.append(footer);
			}

			tagUtils.write(pageContext, results.toString());
		}

		if ( exceptionBuffer != null ) {
			if ( stackTraceVar != null ) {
				int varScope = PageContext.PAGE_SCOPE;
				if ( stackTraceVarScope != null ) {
					varScope = tagUtils.getScope(stackTraceVarScope);
				}
				pageContext.setAttribute(stackTraceVar, exceptionBuffer.toString(), varScope);
			} else {
				tagUtils.write(pageContext, new StringBuffer("<pre>").append(exceptionBuffer).append("</pre>").toString());
			}
		}
	}

	/**
	 * Returns <code>true</code> if specified ActionMessages object is not null and is not empty
	 *
	 * @param messages ActionMessages object to check
	 * @return <code>true</code> if specified ActionMessages object is not null and is not empty
	 */
	protected boolean containRequestedMessage(ActionMessages messages) {
		if ( messages == null ) {
			return false;
		}

		Iterator reports = null;
		if ( property == null ) {
			reports = messages.get();
		} else {
			reports = messages.get(property);
		}
		return reports != null && reports.hasNext();
	}

    /**
     * Logs the thrown exception
     *
     * @param t exception to log
     */
	protected void logException(Throwable t) {
		StringWriter sw = new StringWriter();
		t.printStackTrace(new PrintWriter(sw));
		if ( log.isErrorEnabled() ) {
			log.error(sw.toString());
		}
	}

}
